/**
 * 
 */
package edu.ucr.cs.dblab.acestore.base.datatypes;

import java.nio.ByteBuffer;

/**
 * Tuple wrapper.
 * 
 * A tuple contains one or more fields. These fields are organized as the
 * following format in bytes:
 * 
 * +------------------+--------------------+--------------------+-----+--------------------+--------+--------+-----+--------+ 
 * | FieldCount:short | Field0EndOfs:short | Field1EndOfs:short | ... | FieldnEndOfs:short | Field0 | Field1 | ... | Fieldn |
 * +------------------+--------------------+--------------------+-----+--------------------+--------+--------+-----+--------+
 * 
 * @author jarod
 * @created Jan 18, 2011
 */
public class TupleWrapper {

	private short[] fieldsEndOffsets;
	private byte[] data;

	public TupleWrapper(int fieldCount) {
		this.fieldsEndOffsets = new short[fieldCount];
		// Initialize the end offset of each field
		for (int i = 0; i < fieldCount; i++) {
			fieldsEndOffsets[i] = (short)(3 + i * 4);
		}
		// Initialize the data fields, with default space of 4 for each field
		this.data = new byte[fieldCount * 4];
	}

	/**
	 * Insert a new value to the specific field.
	 * 
	 * @param value
	 * @param field
	 */
	public void insert(byte[] value, int field) {
		// Get the end offset of previous field
		int endOffset = field == 0 ? -1 : fieldsEndOffsets[field - 1];
		// Get the difference on the length
		int lenDiff = value.length
				- (fieldsEndOffsets[field] - endOffset);
		// Create a new byte array with more space
		byte[] newdata = new byte[data.length + lenDiff];
		// Copy fields before the updating field
		System.arraycopy(data, 0, newdata, 0, endOffset + 1);
		// Copy the updating field
		System.arraycopy(value, 0, newdata, endOffset + 1, value.length);
		// Copy the fields after the updating field
		if (field + 1 < fieldsEndOffsets.length)
			System.arraycopy(data, fieldsEndOffsets[field] + 1, newdata,
					endOffset + value.length + 1,
					fieldsEndOffsets[fieldsEndOffsets.length - 1]
							- fieldsEndOffsets[field]);
		// Update the end offset of the updating field
		fieldsEndOffsets[field] = (short)(endOffset + value.length);
		// Update the end offsets of fields after the updating field
		for (int i = field + 1; i < fieldsEndOffsets.length; i++) {
			fieldsEndOffsets[i] += lenDiff;
		}
		this.data = newdata;
	}
	
	/**
	 * Add a new field with specified value.
	 * 
	 * @param value
	 */
	public void add(byte[] value){
		// Create a new field.
		short[] newFields = new short[fieldsEndOffsets.length + 1];
		// Copy the original fields into the new fields
		System.arraycopy(fieldsEndOffsets, 0, newFields, 0, fieldsEndOffsets.length);
		// Create a new data buf with extended space
		byte[] newdata = new byte[data.length + value.length];
		// Copy original data into the new buffer
		System.arraycopy(data, 0, newdata, 0, data.length);
		// Copy the new field into the new buffer
		System.arraycopy(value, 0, newdata, data.length, value.length);
		// Update the end offset of the new field
		newFields[fieldsEndOffsets.length] = (short)(newFields[fieldsEndOffsets.length - 1] + value.length);
		// Update the wrapper.
		this.fieldsEndOffsets = newFields;
		this.data = newdata;
	}
	
	public byte[] getBytes(){
		byte[] output = new byte[data.length + fieldsEndOffsets.length * Short.SIZE / 8 + Short.SIZE / 8];
		ByteBuffer bbf = ByteBuffer.wrap(output);
		bbf.putShort((short)(fieldsEndOffsets.length));
		for(int i = 0; i < fieldsEndOffsets.length; i++){
			bbf.putShort((i + 1) * Short.SIZE / 8, fieldsEndOffsets[i]);
		}
		bbf.position(fieldsEndOffsets.length * Short.SIZE / 8 + Short.SIZE / 8);
		bbf.put(data);
		return output;
	}
	
	public static void main(String[] args) {
		TupleWrapper test = new TupleWrapper(2);
		test.insert(IntegerSerDeserializer.SERIALIZE(20), 1);
		test.insert(IntegerSerDeserializer.SERIALIZE(10), 0);
		test.add(IntegerSerDeserializer.SERIALIZE(30));
		ByteBuffer data = ByteBuffer.wrap(test.getBytes());
		System.out.println("Field Count: " + data.getShort());
		System.out.println("Field End Offsets: " 
				+ data.getShort(2) + "\t"
				+ data.getShort(4) + "\t"
				+ data.getShort(6) + "\t");
		System.out.println("Fields: " 
				+ data.getInt(8) + "\t"
				+ data.getInt(12) + "\t"
				+ data.getInt(16) + "\t");
		test.insert(IntegerSerDeserializer.SERIALIZE(1532), 1);
		data = ByteBuffer.wrap(test.getBytes());
		System.out.println("Field Count: " + data.getShort());
		System.out.println("Field End Offsets: " 
				+ data.getShort(2) + "\t"
				+ data.getShort(4) + "\t"
				+ data.getShort(6) + "\t");
		System.out.println("Fields: " 
				+ data.getInt(8) + "\t"
				+ data.getInt(12) + "\t"
				+ data.getInt(16) + "\t");
	}

}
